/*
 *
 *  * Unidata Platform
 *  * Copyright (c) 2013-2020, UNIDATA LLC, All rights reserved.
 *  *
 *  * Commercial License
 *  * This version of Unidata Platform is licensed commercially and is the appropriate option for the vast majority of use cases.
 *  *
 *  * Please see the Unidata Licensing page at: https://unidata-platform.com/license/
 *  * For clarification or additional options, please contact: info@unidata-platform.com
 *  * -------
 *  * Disclaimer:
 *  * -------
 *  * THIS SOFTWARE IS DISTRIBUTED "AS-IS" WITHOUT ANY WARRANTIES, CONDITIONS AND
 *  * REPRESENTATIONS WHETHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE
 *  * IMPLIED WARRANTIES AND CONDITIONS OF MERCHANTABILITY, MERCHANTABLE QUALITY,
 *  * FITNESS FOR A PARTICULAR PURPOSE, DURABILITY, NON-INFRINGEMENT, PERFORMANCE AND
 *  * THOSE ARISING BY STATUTE OR FROM CUSTOM OR USAGE OF TRADE OR COURSE OF DEALING.
 *
 */

package org.unidata.mdm.dq.data.service.impl;

import java.util.Objects;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.unidata.mdm.core.context.ModelRefreshContext;
import org.unidata.mdm.core.service.MetaModelService;
import org.unidata.mdm.core.service.ModelRefreshListener;
import org.unidata.mdm.core.type.model.StorageElement;
import org.unidata.mdm.core.util.SecurityUtils;
import org.unidata.mdm.data.configuration.DataNamespace;
import org.unidata.mdm.dq.core.configuration.DataQualityDescriptors;
import org.unidata.mdm.dq.core.configuration.DataQualityModelIds;
import org.unidata.mdm.dq.core.type.model.instance.DataQualityInstance;
import org.unidata.mdm.dq.core.type.model.instance.NamespaceAssignmentElement;
import org.unidata.mdm.dq.data.configuration.DataQualityDataConfigurationConstants;
import org.unidata.mdm.dq.data.type.search.RecordsDataQualityHeaderField;
import org.unidata.mdm.meta.configuration.Descriptors;
import org.unidata.mdm.meta.type.instance.DataModelInstance;
import org.unidata.mdm.meta.type.search.EntityIndexType;
import org.unidata.mdm.search.context.MappingRequestContext;
import org.unidata.mdm.search.service.SearchService;
import org.unidata.mdm.search.type.mapping.Mapping;
import org.unidata.mdm.search.type.mapping.impl.CompositeMappingField;
import org.unidata.mdm.search.type.mapping.impl.StringMappingField;
import org.unidata.mdm.system.service.AfterPlatformStartup;
import org.unidata.mdm.system.service.ConfigurationActionService;
import org.unidata.mdm.system.type.action.ConfigurationAction;

/**
 * @author Alexey Tsarapkin
 */
@Component
public class DataQualityDataMappingComponent implements ModelRefreshListener, AfterPlatformStartup {
    /**
     * The logger.
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(DataQualityDataMappingComponent.class);
    /**
     * Config action.
     */
    private final ConfigurationAction ensureMappingAction = new ConfigurationAction() {
        /**
         * {@inheritDoc}
         */
        @Override
        public int getTimes() {
            return 1;
        }
        /**
         * {@inheritDoc}
         */
        @Override
        public String getId() {
            return DataQualityDataConfigurationConstants.DATA_QUALITY_DATA_ENSURE_INDEXES_ACTION_NAME;
        }
        /**
         * {@inheritDoc}
         */
        @Override
        public boolean execute() {

            try {

                for (StorageElement sme : metaModelService.getStorageInstance().getActive()) {
                    appendMappingsForStorageId(sme.getStorageId());
                }

            } catch (Exception e) {
                LOGGER.warn("Cannot append DQ data mappings to search indexes. Exception caught.", e);
                return false;
            }

            return true;
        }


    };
    /**
     * CAS.
     */
    @Autowired
    private ConfigurationActionService configurationActionService;
    /**
     * The SS.
     */
    @Autowired
    private SearchService searchService;
    /**
     * The MMS.
     */
    @Autowired
    private MetaModelService metaModelService;

    @Override
    public void afterPlatformStartup() {
        configurationActionService.execute(ensureMappingAction);
        metaModelService.listener(this);
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public void refresh(ModelRefreshContext refresh) {
        appendMappingsForStorageId(SecurityUtils.getStorageId(refresh));
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public String getTypeId() {
        return DataQualityModelIds.DATA_QUALITY;
    }

    private void appendMappingsForStorageId(String storageId) {

        DataModelInstance di = metaModelService.instance(Descriptors.DATA, storageId, null);
        DataQualityInstance qi = metaModelService.instance(DataQualityDescriptors.DQ, storageId, null);
        if (Objects.isNull(di) || Objects.isNull(qi)) {
            return;
        }

        appendMappings(di, qi);
    }

    private void appendMappings(DataModelInstance di, DataQualityInstance qi) {

        NamespaceAssignmentElement la = qi.getAssignment(DataNamespace.LOOKUP);
        if (Objects.nonNull(la)) {
            for (String entityName : la.getEntityNames()) {

                if (!di.isLookup(entityName)) {
                    continue;
                }

                appendMapping(entityName);
            }
        }

        NamespaceAssignmentElement ra = qi.getAssignment(DataNamespace.REGISTER);
        if (Objects.nonNull(ra)) {
            for (String entityName : ra.getEntityNames()) {

                if (!di.isRegister(entityName)) {
                    continue;
                }

                appendMapping(entityName);
            }
        }
    }

    private void appendMapping(String entityName) {

        final Mapping mapping = new Mapping(EntityIndexType.RECORD)
            .withFields(
                new CompositeMappingField(RecordsDataQualityHeaderField.FIELD_QUALITY_ERRORS.getName())
                    .withNested(true)
                    .withFields(
                            // 1. Search
                            new StringMappingField(RecordsDataQualityHeaderField.FIELD_SET_NAME.getName())
                                .withDocValue(true),
                            new StringMappingField(RecordsDataQualityHeaderField.FIELD_SET_DISPLAY_NAME.getName())
                                .withAnalyzed(true)
                                .withMorphologicalAnalysis(true),
                            new StringMappingField(RecordsDataQualityHeaderField.FIELD_RULE_NAME.getName())
                                .withDocValue(true),
                            new StringMappingField(RecordsDataQualityHeaderField.FIELD_RULE_DISPLAY_NAME.getName())
                                .withAnalyzed(true)
                                .withMorphologicalAnalysis(true),
                            new StringMappingField(RecordsDataQualityHeaderField.FIELD_FUNCTION_NAME.getName())
                                .withDocValue(true),
                            new StringMappingField(RecordsDataQualityHeaderField.FIELD_MESSAGE.getName())
                                .withAnalyzed(true)
                                .withMorphologicalAnalysis(true),
                            new StringMappingField(RecordsDataQualityHeaderField.FIELD_SEVERITY.getName())
                                .withDocValue(true),
                            new StringMappingField(RecordsDataQualityHeaderField.FIELD_SCORE.getName())
                                .withDocValue(true),
                            new StringMappingField(RecordsDataQualityHeaderField.FIELD_TOTAL_SCORE.getName())
                                .withDocValue(true),
                            new StringMappingField(RecordsDataQualityHeaderField.FIELD_CATEGORY.getName())
                                .withDocValue(true),
                             // 2. Data
                            new StringMappingField(RecordsDataQualityHeaderField.FIELD_SPOT.getName())
                                .withIndexed(false),
                            new StringMappingField(RecordsDataQualityHeaderField.FIELD_ERROR.getName())
                                .withIndexed(false)));

        MappingRequestContext mCtx = MappingRequestContext.builder()
                .entity(entityName)
                .storageId(SecurityUtils.getCurrentUserStorageId())
                .mapping(mapping)
                .build();

        searchService.process(mCtx);
    }
}
